#include <vector>
#include <utility>
#include <algorithm>

#include "GError.h"
#include "GPreProcessor.h"
#include "GPreProcessorRepeater.h"


namespace GUtil
{
	#define G_DLG_COND_COMMA						G_PP_COMMA_IF(G_PP_INCLUDE_INDEX)

	// Generates typename ArgType0, typename ArgType1, typename ArgType2, ...
	#define G_DLG_TEMP_ARGUMENT(INDEX, PARAMETERS)	G_PP_COMMA_IF(INDEX) typename ArgType##INDEX
	#define G_DLG_TEMP_ARGUMENT_LIST				G_PP_MACRO_CHAIN(G_DLG_TEMP_ARGUMENT, 0, G_PP_INCLUDE_INDEX)
	
	// Generates ArgType0, ArgType1, ArgType1, ...
	#define G_DLG_FUNC_ARG_TYPE(INDEX, PARAMETERS)	G_PP_COMMA_IF(INDEX) ArgType##INDEX
	#define G_DLG_FUNC_ARG_TYPE_LIST				G_PP_MACRO_CHAIN(G_DLG_FUNC_ARG_TYPE, 0, G_PP_INCLUDE_INDEX)
	
	// Generates Arg0, Arg1, Arg2, ...
	#define G_DLG_FUNC_ARGUMENT(INDEX, PARAMETERS)	G_PP_COMMA_IF(INDEX) Arg##INDEX
	#define G_DLG_FUNC_ARGUMENT_LIST				G_PP_MACRO_CHAIN(G_DLG_FUNC_ARGUMENT, 0, G_PP_INCLUDE_INDEX)
	
	// Generates ArgType0 Arg0, ArgType1 Arg1, ArgType2 Arg2, ...
	#define G_DLG_FUNC_PARAMETER(INDEX, PARAMETERS)	G_PP_COMMA_IF(INDEX) ArgType##INDEX Arg##INDEX
	#define G_DLG_FUNC_PARAMETER_LIST				G_PP_MACRO_CHAIN(G_DLG_FUNC_PARAMETER, 0, G_PP_INCLUDE_INDEX)

	/************************************************************************/
	/*								GDelegate								*/
	/************************************************************************/

	template <typename ReturnType G_DLG_COND_COMMA G_DLG_TEMP_ARGUMENT_LIST>
	class GDelegate <ReturnType (G_DLG_FUNC_ARG_TYPE_LIST)>
	{
		union InstancePtr
		{
			void*			Value;
			const void*		ValueConst;
		};

		typedef ReturnType (*InternalFuctionPtr)(InstancePtr G_DLG_COND_COMMA G_DLG_FUNC_ARG_TYPE_LIST);
	
		typedef std::pair<InstancePtr, InternalFuctionPtr> GDelegateStub;

	private:
		GDelegateType		Type;
		GDelegateStub		Stub;

		template <ReturnType (*Function)(G_DLG_FUNC_ARG_TYPE_LIST)>
		static __forceinline ReturnType StubFunction(InstancePtr Ptr G_DLG_COND_COMMA G_DLG_FUNC_PARAMETER_LIST)
		{
			return (Function)(G_DLG_FUNC_ARGUMENT_LIST);
		}

		template <typename ClassType, ReturnType (ClassType::*Method)(G_DLG_FUNC_ARG_TYPE_LIST)>
		static __forceinline ReturnType StubMethod(InstancePtr Ptr G_DLG_COND_COMMA G_DLG_FUNC_PARAMETER_LIST)
		{
			return (static_cast<ClassType*>(Ptr.Value)->*Method)(G_DLG_FUNC_ARGUMENT_LIST);
		}

		template <typename ClassType, ReturnType (ClassType::*ConstMethod)(G_DLG_FUNC_ARG_TYPE_LIST) const>
		static __forceinline ReturnType StubConstMethod(InstancePtr Ptr G_DLG_COND_COMMA G_DLG_FUNC_PARAMETER_LIST)
		{
			return (static_cast<const ClassType*>(Ptr.ValueConst)->*ConstMethod)(G_DLG_FUNC_ARGUMENT_LIST);
		}

	public:
		GDelegate()
		{
			Stub.first.Value = NULL;
			Stub.second = NULL;

			Type = G_DT_UNBOUND;
		}

		GDelegateType GetType() const
		{
			return Type;
		}

		template <ReturnType (*Function)(G_DLG_FUNC_ARG_TYPE_LIST)>
		void Bind()
		{
			Stub.first.Value = NULL;
			Stub.second = &StubFunction<Function>;

			Type = G_DT_FUNCTION;
		}

		template <typename ClassType, ReturnType (ClassType::*Method)(G_DLG_FUNC_ARG_TYPE_LIST)>
		void Bind(ClassType* Instance)
		{
			Stub.first.Value = Instance;
			Stub.second = &StubMethod<ClassType, Method>;

			Type = G_DT_METHOD;
		}

		template <typename ClassType, ReturnType (ClassType::*ConstMethod)(G_DLG_FUNC_ARG_TYPE_LIST) const>
		void Bind(const ClassType* Instance)
		{
			Stub.first.ValueConst = Instance;
			Stub.second = &StubConstMethod<ClassType, ConstMethod>;

			Type = G_DT_METHOD_CONST;
		}

		void Unbind()
		{
			Stub.first.Value = NULL;
			Stub.second = NULL;

			Type = G_DT_UNBOUND;
		}

		ReturnType Invoke(G_DLG_FUNC_PARAMETER_LIST)
		{
			GError::DebugCheck(Type == G_DT_UNBOUND, "Cannot invoke unbound delegate.");

			return Stub.second(Stub.first G_DLG_COND_COMMA G_DLG_FUNC_ARGUMENT_LIST);
		}

		bool operator == (const GDelegate <ReturnType (G_DLG_FUNC_ARG_TYPE_LIST)>& Other)
		{
			return (Other.Stub.first.Value == Stub.first.Value) && (Other.Stub.second == Stub.second);
		}

		const GDelegate <ReturnType (G_DLG_FUNC_ARG_TYPE_LIST)>& operator = (const GDelegate <ReturnType (G_DLG_FUNC_ARG_TYPE_LIST)>& Other)
		{
			Type = Other.Type;
			Stub = Other.Stub;

			return *this;
		}

	}; // class GDelegate


	/************************************************************************/
	/*							GMultiDelegate								*/
	/************************************************************************/

	template <G_DLG_TEMP_ARGUMENT_LIST>
	class GMultiDelegate <void (G_DLG_FUNC_ARG_TYPE_LIST)>
	{
	private:
		typedef GDelegate<void (G_DLG_FUNC_ARG_TYPE_LIST)>	DelegateType;
		typedef std::vector<DelegateType> GDelegateArray;

		GDelegateArray	Delegates;

	public:
		GMultiDelegate()
		{
	
		}

		~GMultiDelegate()
		{
			Delegates.clear();
		}

		GUInt32 GetDelegateCount() const
		{
			Delegates.size();
		}

		void Append(DelegateType& Delegate)
		{
			Delegates.push_back(Delegate);
		}

		template <void (*Function) (G_DLG_FUNC_ARG_TYPE_LIST)>
		void Append()
		{
			DelegateType Delegate;
			Delegate.Bind<Function>();

			Delegates.push_back(Delegate);
		}

		template <typename ClassType, void (ClassType::*Method)(G_DLG_FUNC_ARG_TYPE_LIST)>
		void Append(ClassType* Instance)
		{
			DelegateType Delegate;
			Delegate.Bind<ClassType, Method>(Instance);

			Delegates.push_back(Delegate);
		}

		template <typename ClassType, void (ClassType::*ConstMethod)(G_DLG_FUNC_ARG_TYPE_LIST) const>
		void Append(const ClassType* Instance)
		{
			DelegateType Delegate;
			Delegate.Bind<ClassType, ConstMethod>(Instance);

			Delegates.push_back(Delegate);
		}

		void Remove(DelegateType& Delegate)
		{
			GDelegateArray::iterator Found = std::find(Delegates.begin(), Delegates.end(), Delegate);

			if (Found != Delegates.end())
			{
				Delegates.erase(Found);
			}
		}

		template <void (*Function) (G_DLG_FUNC_ARG_TYPE_LIST)>
		void Remove()
		{
			DelegateType Delegate;
			Delegate.Bind<Function>();

			GDelegateArray::iterator Found = std::find(Delegates.begin(), Delegates.end(), Delegate);

			if (Found != Delegates.end())
			{
				Delegates.erase(Found);
			}
		}

		template <typename ClassType, void (ClassType::*Method)(G_DLG_FUNC_ARG_TYPE_LIST)>
		void Remove(ClassType* Instance)
		{
			DelegateType Delegate;
			Delegate.Bind<ClassType, Method>(Instance);

			GDelegateArray::iterator Found = std::find(Delegates.begin(), Delegates.end(), Delegate);

			if (Found != Delegates.end())
			{
				Delegates.erase(Found);
			}
		}

		template <typename ClassType, void (ClassType::*ConstMethod)(G_DLG_FUNC_ARG_TYPE_LIST) const>
		void Remove(const ClassType* Instance)
		{
			DelegateType Delegate;
			Delegate.Bind<ClassType, ConstMethod>(Instance);

			GDelegateArray::iterator Found = std::find(Delegates.begin(), Delegates.end(), Delegate);

			if (Found != Delegates.end())
			{
				Delegates.erase(Found);
			}
		}

		template <void (*Function) (G_DLG_FUNC_ARG_TYPE_LIST)>
		DelegateType* Find()
		{
			DelegateType Delegate;
			Delegate.Bind<Function>();

			GDelegateArray::iterator Found = std::find(Delegates.begin(), Delegates.end(), Delegate);

			if (Found != Delegates.end())
			{
				return &(*Found);
			}

			return NULL;
		}

		template <typename ClassType, void (ClassType::*Method)(G_DLG_FUNC_ARG_TYPE_LIST)>
		DelegateType* Find(ClassType* Instance)
		{
			DelegateType Delegate;
			Delegate.Bind<ClassType, Method>(Instance);

			GDelegateArray::iterator Found = std::find(Delegates.begin(), Delegates.end(), Delegate);

			if (Found != Delegates.end())
			{
				return &(*Found);
			}

			return NULL;
		}

		template <typename ClassType, void (ClassType::*ConstMethod)(G_DLG_FUNC_ARG_TYPE_LIST) const>
		DelegateType* Find(const ClassType* Instance, DelegateType& Output)
		{
			DelegateType Delegate;
			Delegate.Bind<ClassType, ConstMethod>(Instance);

			GDelegateArray::iterator Found = std::find(Delegates.begin(), Delegates.end(), Delegate);

			if (Found != Delegates.end())
			{
				return &(*Found);
			}

			return NULL;
		}

		void Trigger(G_DLG_FUNC_PARAMETER_LIST)
		{
			GDelegateArray::iterator Current = Delegates.begin();
			GDelegateArray::iterator End = Delegates.end();
		
			while (Current != End)
			{
				(*Current).Invoke(G_DLG_FUNC_ARGUMENT_LIST);

				Current++;
			}
		}

	}; // class GEvent

	
	#undef G_DLG_COND_COMMA

	#undef G_DLG_TEMP_ARGUMENT
	#undef G_DLG_TEMP_ARGUMENT_LIST

	#undef G_DLG_FUNC_ARG_TYPE
	#undef G_DLG_FUNC_ARG_TYPE_LIST

	#undef G_DLG_FUNC_ARGUMENT
	#undef G_DLG_FUNC_ARGUMENT_LIST

	#undef G_DLG_FUNC_PARAMETER
	#undef G_DLG_FUNC_PARAMETER_LIST


}; // namespace GUtil

